﻿using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using NodeBasedSkillEditor.Model;
using NodeBasedSkillEditor.View;
using XDFLib.Extensions.Reflections;
using CommunityToolkit.Mvvm.Input;
using System.Collections;

namespace NodeBasedSkillEditor.ViewModel
{
    public class VM_Node : ObservableObject
    {
        public class PortTypeGroup
        {
            public HashSet<Type> TypeWhiteList = new();
            public HashSet<VM_Port> PortMembers = new();
        }

        private string _nodeName;
        public string NodeName {
            get { return _nodeName; }
            set { _nodeName = value; OnPropertyChanged(); }
        }

        private Point _position;
        public Point Position {
            get { return _position; }
            set {
                _position = value; OnPropertyChanged();
                if (BindNodeData != null)
                {
                    BindNodeData.Position = Position.ToVector2();
                }
                OnPortPositionChanged?.Invoke();
            }
        }

        public event Action<uint, string, string> OnDescriptionChanged;
        private string _description;
        public string Description {
            get { return _description; }
            set {
                var oldDesc = _description;
                _description = value;
                OnPropertyChanged();
                if (BindNodeData != null)
                {
                    BindNodeData.Description = _description;
                }
                OnDescriptionChanged?.Invoke(LocalId, oldDesc, value);
            }
        }
        public void ChangeDescriptionWithoutChangeHistory(string desc)
        {
            _description = desc;
            OnPropertyChanged("Description");
            if (BindNodeData != null)
            {
                BindNodeData.Description = _description;
            }
        }

        private bool _isSelected;
        public bool IsSelected {
            get { return _isSelected; }
            set { _isSelected = value; OnPropertyChanged(); }
        }

        private bool _isEntry;
        public bool IsEntry {
            get { return _isEntry; }
            set { _isEntry = value; OnPropertyChanged(); }
        }

        public RelayCommand OnSetAsEntryCmd { get; private set; }

        private SolidColorBrush _nodeColor = new SolidColorBrush();
        public SolidColorBrush NodeColor {
            get { return _nodeColor; }
            set { _nodeColor = value; OnPropertyChanged(); }
        }

        public uint LocalId { get; private set; }
        public VM_DAG ParentDAG { get; private set; }
        public NodeData BindNodeData { get; private set; }

        public NodeControl BindingNodeCtrl { get; private set; }

        /// <summary>Key为Port.LocalId</summary>
        public Dictionary<uint, VM_Port> AllPorts { get; private set; } = new Dictionary<uint, VM_Port>(4);
        /// <summary>Key为PortClusterGroup.LocalId，与Port.LocalId共用id空间</summary>
        public Dictionary<uint, VM_PortClusterGroup> AllPortClusterGroups { get; private set; } = new Dictionary<uint, VM_PortClusterGroup>();

        public HashSet<VM_Port> RelayContextPorts { get; private set; } = new HashSet<VM_Port>(4);

        private Dictionary<int, TypeGroupInfo> _typeGroupInfos = new();

        public Windows.NodeInspector RelativeInspector = null;

        public event Action OnPortPositionChanged;

        //public bool IsCtrlReady { get { return _waitingCtrlsToReady.Count == 0; } }
        //private HashSet<ICtrlAsyncReady> _waitingCtrlsToReady = new HashSet<ICtrlAsyncReady>();

        private uint _topPortLocalId = DAGHelper.MinPortId;

        public VM_Node(NodeData nodeData, VM_DAG parentDag)
        {
            OnSetAsEntryCmd = new RelayCommand(OnSetAsEntry);
            //_waitingCtrlsToReady.Add(this);
            LocalId = nodeData.LocalId;
            ParentDAG = parentDag;
            BindNodeData = nodeData;
            Position = PointExtension.Vector2ToPoint(BindNodeData.Position);
            InitializeTypeGroups();
        }

        private void OnSetAsEntry()
        {
            ParentDAG.SetEntry(LocalId);
        }

        private void InitializeTypeGroups()
        {
            foreach (var attr in BindNodeData.GetType().GetCustomAttributes<NodeTypeGroupAttribute>())
            {
                if (attr.AllowedTypes.Length > 0)
                {
                    TypeGroupInfo tgi;
                    if (!_typeGroupInfos.TryGetValue(attr.TypeGroupId, out tgi))
                    {
                        tgi = new() { Id = attr.TypeGroupId };
                        _typeGroupInfos.Add(attr.TypeGroupId, tgi);
                    }

                    foreach (var t in attr.AllowedTypes)
                    {
                        tgi.AllowedTypes.Add(t);
                    }
                }
            }
        }

        public void InitializeBindingNodeCtrl(NodeControl ctrl)
        {
            ctrl.DataContext = this;
            BindingNodeCtrl = ctrl;
            //BindingNodeCtrl.Loaded += OnBindCtrlLoaded;
            BindingNodeCtrl.SizeChanged += OnNodeCtrlSizeChanged;
            InitializeFromNodeData(BindNodeData);
        }

        //private void OnBindCtrlLoaded(object sender, RoutedEventArgs e)
        //{
        //    _waitingCtrlsToReady.Remove(this);
        //}

        public void OnLMBDown(MouseButtonEventArgs e)
        {
            ParentDAG.OnLMBDownOnNode(e, this);
        }

        public void OnLMBUp(MouseButtonEventArgs e)
        {
            ParentDAG.OnLMBUpOnNode(e, this);
        }

        public void OnRMBDown(MouseButtonEventArgs e)
        {
            ParentDAG.OnRMBDownOnNode(e, this);
        }

        public void OnRMBUp(MouseButtonEventArgs e)
        {
            ParentDAG.OnRMBUpOnNode(e, this);
        }

        public void OnDoubleClick(MouseButtonEventArgs e)
        {
            ParentDAG.OnDoubleClickNode(e, this);
        }

        public void TryRefreshRelativeInspector()
        {
            RelativeInspector?.NeedRefresh();
        }

        public void OpenContextMenu()
        {
            BindingNodeCtrl.ContextMenu.DataContext = this;
            BindingNodeCtrl.ContextMenu.IsOpen = true;
        }

        //void OnMemberCtrlsReady(ICtrlAsyncReady ctrl)
        //{
        //    _waitingCtrlsToReady.Remove(ctrl);
        //}

        void InitializeFromNodeData(NodeData nodeData)
        {
            Position = PointExtension.Vector2ToPoint(BindNodeData.Position);
            var attr = nodeData.GetType().GetCustomAttribute<NodeAttribute>();
            NodeName = attr.Name;
            Description = nodeData.Description;
            NodeColor.Color = DAGHelper.GetNodeColorByCategory(attr.Category);
            LocalId = nodeData.LocalId;

            var nodeDataRef = __makeref(nodeData);
            var nodeFields = nodeData.GetType().GetFields();
            foreach (var nodeField in nodeFields)
            {
                if (nodeField.FieldType.IsAssignableTo(typeof(BasePort)))
                {
                    if (nodeField.FieldType != typeof(PortGroup))
                    {
                        var currPort = nodeField.GetValueDirect(nodeDataRef) as BasePort;
                        GenerateNewPortFromField(nodeField, currPort);
                    }
                    else
                    {
                        var currPortGroup = nodeField.GetValueDirect(nodeDataRef) as PortGroup;
                        GenerateNewPortGroupFromField(nodeField, currPortGroup);
                    }
                }
                else if (nodeField.FieldType == typeof(PortClusterGroup))
                {
                    var clusterGroupField = nodeField.GetCustomAttribute<PortClusterGroupAttribute>();
                    var portCluster = nodeField.GetValue(nodeData) as PortClusterGroup;
                    GenerateNewPortClusterFromField(clusterGroupField.Name, portCluster);
                }
            }
        }

        void GenerateNewPortFromField(FieldInfo portField, BasePort nodePort)
        {
            var attr = portField.GetCustomAttribute<PortAttribute>();

            if (attr != null && attr.IOType != EPortIOType.Unknown)
            {
                //// 注意，在数据被保存的时候，所有的id已经被做过梳理，这里的操作，是为了额外增加保险，让id不会冲突
                //nodePort.PortLocalId = GetNextValidPortLocalId(nodePort.PortLocalId);
                nodePort.PortLocalId = GetNextValidPortLocalId(nodePort.PortLocalId);
                var portName = portField.Name;

                EPortIOType ioType = attr.IOType;
                int typeGroup = attr.TypeGroup;

                var portVM = new VM_Port(nodePort.PortLocalId, this, portName, ioType, nodePort, typeGroup,
                    attr.ContextMode, attr.IsListPort);

                PortControl newPortCtrl = (ioType == EPortIOType.Input) ? new PortControl_Input() : new PortControl_Output();
                portVM.SetupBindingPortCtrl(newPortCtrl);
                AddPortOnInitialization(portVM, newPortCtrl);
            }
        }

        void GenerateNewPortGroupFromField(FieldInfo portGroupField, PortGroup portGroup)
        {
            var attr = portGroupField.GetCustomAttribute<PortAttribute>();
            if (attr != null && attr.IOType != EPortIOType.Unknown)
            {
                // 注意，在数据被保存的时候，所有的id已经被做过梳理，这里的操作，是为了额外增加保险，让id不会冲突
                portGroup.PortLocalId = GetNextValidPortLocalId(portGroup.PortLocalId);
                portGroup.ExpandPortData.PortLocalId = portGroup.PortLocalId;
                var portName = portGroupField.Name;

                EPortIOType ioType = attr.IOType;
                int typeGroup = attr.TypeGroup;

                var expandPortVM = new VM_Port(portGroup.PortLocalId, this, portName, ioType, portGroup.ExpandPortData,
                    typeGroup, attr.ContextMode, attr.IsListPort);
                var PortGroupCtrl = new PortGroupControl();
                PortGroupCtrl.SizeChanged += OnPortGroupSizeChanged;
                var portGroupVM = new VM_PortGroup(expandPortVM, portGroup, PortGroupCtrl, attr.TypeGroup);
                //portGroupVM.SetupBindingControl(PortGroupCtrl);

                AddPortOnInitialization(expandPortVM, PortGroupCtrl);
            }
        }

        void GenerateNewPortClusterFromField(string groupName, PortClusterGroup clusterGroup)
        {
            var clusterGroupName = groupName;
            var clusterGroupCtrl = new PortClusterGroupControl();
            BindingNodeCtrl.ClusterGroupStack.Children.Add(clusterGroupCtrl);
            clusterGroup.LocalId = GetNextValidPortLocalId(clusterGroup.LocalId, true);
            var clusterGroupVM = new VM_PortClusterGroup(clusterGroupName, clusterGroup, this, clusterGroupCtrl);
            AllPortClusterGroups.Add(clusterGroup.LocalId, clusterGroupVM);
        }

        void AddPortOnInitialization(VM_Port portVM, Control ctrl)
        {
            AddPort(portVM);
            AddPortCtrl(portVM, ctrl);
        }

        public void AddPort(VM_Port portVM, bool tryAlignPortTypeWithTypeGroup = false)
        {
            AllPorts.Add(portVM.LocalId, portVM);

            if (portVM.CanRelayContext)
            {
                RelayContextPorts.Add(portVM);
            }

            if (portVM.TypeGroupId != DAGHelper.InvalidTypeGroupId)
            {
                if (tryAlignPortTypeWithTypeGroup)
                {
                    var tgi = GetTypeGroupInfo(portVM.TypeGroupId);
                    if (tgi != null && tgi.PortMembers.Count >0)
                    {
                        var destType = tgi.GetCurrentPortDataType();
                        TryChangePortToType(portVM, destType);
                    }
                }
                AddPortToTypeGroupInfo(portVM.TypeGroupId, portVM);
            }
        }

        void AddPortCtrl(VM_Port portVM, Control ctrl)
        {
            if (portVM.IOType == EPortIOType.Input)
            {
                BindingNodeCtrl.InputStack.Children.Add(ctrl);
            }
            else if (portVM.IOType == EPortIOType.Output)
            {
                BindingNodeCtrl.OutputStack.Children.Add(ctrl);
            }
        }

        public void OnNodeCtrlSizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateAllPortPositions();
        }

        public void OnPortGroupSizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateAllPortPositions();
            TryRefreshRelativeInspector();
        }

        bool AddPortToTypeGroupInfo(int typeGroupId, VM_Port port)
        {
            if (_typeGroupInfos.TryGetValue(typeGroupId, out TypeGroupInfo tgi))
            {
                tgi.PortMembers.Add(port);
                return true;
            }
            return false;
        }

        bool RemovePortFromTypeGroupInfo(int typeGroupId, VM_Port port)
        {
            if (_typeGroupInfos.TryGetValue(typeGroupId, out TypeGroupInfo tgi))
            {
                return tgi.PortMembers.Remove(port);
            }
            return false;
        }

        public TypeGroupInfo GetTypeGroupInfo(int typeGroupId)
        {
            _typeGroupInfos.TryGetValue(typeGroupId, out TypeGroupInfo tgi);
            return tgi;
        }

        public VM_Port IncreseNewPortByExpandPort(VM_Port expandPort)
        {
            if (expandPort.IsExpandPort && expandPort.ParentGroup != null)
            {
                var newPortLocalId = GenerateNewPortId();
                var newPortVM = expandPort.ParentGroup.IncreasePort(newPortLocalId);

                if (newPortVM.CanRelayContext)
                {
                    RelayContextPorts.Add(newPortVM);
                }
                if (newPortVM.TypeGroupId != DAGHelper.InvalidTypeGroupId)
                {
                    AddPortToTypeGroupInfo(newPortVM.TypeGroupId, newPortVM);
                }
                return newPortVM;
            }
            else return null;
        }

        public VM_Port RecorverSubPort(BasePort portData, uint groupLocalId, int indexInGroup)
        {
            var expandPort = GetPort(groupLocalId);
            if (expandPort != null && expandPort.IsExpandPort)
            {
                TryChangePortToType(expandPort, portData.GetDataType());
                var newPortVM = expandPort.ParentGroup.InsertPort(portData, indexInGroup);

                if (newPortVM.CanRelayContext)
                {
                    RelayContextPorts.Add(newPortVM);
                }
                if (newPortVM.TypeGroupId != DAGHelper.InvalidTypeGroupId)
                {
                    AddPortToTypeGroupInfo(newPortVM.TypeGroupId, newPortVM);
                }
                return newPortVM;
            }
            return null;
        }

        public void DeletePort(VM_Port port)
        {
            if (port.IsNormalPortInGroup)
            {
                port.ParentGroup.DeletePort(port);
                if (port.CanRelayContext)
                {
                    RelayContextPorts.Remove(port);
                }
                if (port.TypeGroupId != DAGHelper.InvalidTypeGroupId)
                {
                    RemovePortFromTypeGroupInfo(port.TypeGroupId, port);
                }
            }
        }

        public void DeletePortOnDeltetingCluster(VM_Port port)
        {
            AllPorts.Remove(port.LocalId);
            if (port.CanRelayContext)
            {
                RelayContextPorts.Remove(port);
            }
            if (port.TypeGroupId != DAGHelper.InvalidTypeGroupId)
            {
                RemovePortFromTypeGroupInfo(port.TypeGroupId, port);
            }
        }

        public void OnSubPortOfGroupChanged(int typeGroupId, int change, VM_Port port)
        {
            var tgi = GetTypeGroupInfo(typeGroupId);
            if (change > 0)
            {
                AllPorts.Add(port.LocalId, port);
                if (tgi != null)
                {
                    tgi.PortMembers.Add(port);
                }
            }
            else if (change < 0)
            {
                AllPorts.Remove(port.LocalId);
                if (tgi != null)
                {
                    tgi.PortMembers.Remove(port);
                }
            }
        }

        public VM_Port GetPort(uint portLocalId)
        {
            VM_Port result;
            AllPorts.TryGetValue(portLocalId, out result);
            return result;
        }

        public VM_PortClusterGroup GetPortClusterGroup(uint portClusterGroupLocalId)
        {
            VM_PortClusterGroup result;
            AllPortClusterGroups.TryGetValue(portClusterGroupLocalId, out result);
            return result;
        }

        public void UpdateAllPortPositions()
        {
            foreach (var port in AllPorts)
            {
                port.Value.BindingPortCtrl.UpdateOffsetAboutParentNode();
            }
            OnPortPositionChanged?.Invoke();
        }

        public bool IsTypeValidForGroup(int typeGroupId, Type destType)
        {
            if (_typeGroupInfos.TryGetValue(typeGroupId, out TypeGroupInfo tgi))
            {
                return tgi.AllowedTypes.Contains(destType);
            }
            else return false;
        }

        public int LinkedPortCountInTypeGroup(int typeGroupId)
        {
            var count = 0;
            foreach (var p in AllPorts)
            {
                if (p.Value.TypeGroupId == typeGroupId && p.Value.IsConnected)
                {
                    count++;
                }
            }
            return count;
        }

        public void TryChangePortToType(VM_Port port, Type dataType)
        {
            if (port.DataType == dataType)
            {
                return;
            }
            else if (AllPorts.ContainsKey(port.LocalId))
            {
                var destPortType = DAGHelper.GetPortTypeViaDataType(dataType);
                if (destPortType == null) { return; }
                if (port.ParentGroup != null)
                {
                    if (port.IsExpandPort)
                    {
                        port.ParentGroup.ResetAndChangePortsToType(destPortType);
                    }
                }
                else
                {
                    LetPortBindToNewTypeData(port, destPortType);
                }
            }
        }

        void LetPortBindToNewTypeData(VM_Port port, Type destPortType)
        {
            var newPortData = Activator.CreateInstance(destPortType) as BasePort;
            newPortData.PortLocalId = port.LocalId;
            BindNodeData.SetFieldValue(port.PortName, newPortData);
            port.BindPortData = newPortData;
        }

        public uint GenerateNewPortId()
        {
            uint newLocalId = DAGHelper.OriginPortId_To_ValidLocalPortId(_topPortLocalId);
            while (AllPorts.ContainsKey(newLocalId))
            {
                _topPortLocalId++;
                newLocalId = DAGHelper.OriginPortId_To_ValidLocalPortId(_topPortLocalId);
                if (_topPortLocalId == DAGHelper.MaxNodeId)
                {
                    MessageBox.Show("节点已用尽最大Id！");
                }
            }
            return newLocalId;
        }

        public uint GetNextValidPortLocalId(uint expectLocalId, bool useTopLocalIdWhenInvalid = false)
        {
            //expectLocalId = Math.Clamp(expectLocalId, DAGHelper.MinPortId, DAGHelper.MaxPortId);
            expectLocalId = DAGHelper.OriginPortId_To_ValidLocalPortId(expectLocalId);

            while (AllPorts.ContainsKey(expectLocalId))
            {
                if (useTopLocalIdWhenInvalid)
                {
                    expectLocalId = DAGHelper.OriginPortId_To_ValidLocalPortId(_topPortLocalId);
                    _topPortLocalId++;
                }
                else { expectLocalId++; }

                if (expectLocalId >= DAGHelper.MaxNodeId)
                {
                    MessageBox.Show("已用尽最大Id！");
                }
            }
            return expectLocalId;
        }
    }
}
